linux非阻塞的socket发送数据出现EAGAIN错误的处理方法 您所在的位置:网站首页 linux socket 非阻塞模式 linux非阻塞的socket发送数据出现EAGAIN错误的处理方法

linux非阻塞的socket发送数据出现EAGAIN错误的处理方法

2023-01-16 06:33| 来源: 网络整理| 查看: 265

linux非阻塞的socket发送数据出现EAGAIN错误的处理方法一、非阻塞socket

​ 非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。

int32_t flags = fcntl(socket_fd, F_GETFL, 0); fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); 二、EAGAIN错误

​ 当应用程序在socket中设置O_NONBLOCK属性后,如果发送缓存被占满,send就会返回EAGAIN或EWOULDBLOCK 的错误。在将socket设置O_NONBLOCK属性后,通过socket发送一个100K大小的数据,第一次成功发送了13140数据,之后继续发送并未成功,errno数值为EAGAIN错误。

三、EPOLL模式下EAGAIN错误处理方式

​ 方法:需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回发送的字节数。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。

当客户通过Socket提供的send函数发送大的数据包时,就可能返回一个EGGAIN的错误。该错误产生的原因是由于send 函数中的size变量大小超过了tcp_sendspace的值。tcp_sendspace定义了应用在调用send之前能够在kernel中缓存的数据量。当应用程序在socket中设置了O_NDELAY或者O_NONBLOCK属性后,如果发送缓存被占满,send就会返回EAGAIN的错误。

为了消除该错误,有三种方法可以选择:   1.调大tcp_sendspace,使之大于send中的size参数   ---no -p -o tcp_sendspace=65536 2.在调用send前,在setsockopt函数中为SNDBUF设置更大的值 3.使用write替代send,因为write没有设置O_NDELAY或者O_NONBLOCK int32_t socket_send(int fd, char* data, int32_t size) { if (NULL == data || size 0) { sended = send(fd, pszTmp, (size_t)remainded, 0); if (sended > 0) { pszTmp += sended; remainded -= sended; } else if (errno == EAGAIN) { continue; } else { break; } } return (size - remainded); }

​ 这种方式并不很完美,当发送大数据的时候,如果客户端一直不调用recv函数接受数据,那么服务器就会卡死在while循环中(持续调用send函数返回EAGAIN错误)。对服务器来说,出现这种情况是致命的,届时服务器的所有功能都不能正常运转。

​ 如果当send函数出现EAGAIN错误的时候,直到当前socket状态变成可写之前,不应该继续调用send函数发送数据。在发送数据之前,将socket的监听的事件增加EPOLLOUT,在数据全部发送之后,再取消EPOLLOUT的监听。

socket监听EPOLLOUT代码:

void epoll_event_mod(int epoll_socket_fd, int fd) { struct epoll_event epollEvent; memset(&epollEvent, 0x0, sizeo(epollEvent)); epollEvent.data.fd = fd; epollEvent.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT; epollEvent.data.ptr = NULL; epoll_ctl(epoll_socket_fd, EPOLL_CTL_MOD, fd, &m_epoll_event); }

socket缓存结构体代码:

struct stSocketBuffer { int32_t m_iHead; int32_t m_iTail; char m_szBuffer[max_socket_buffer_size]; };

socket待发送数据放入缓存结构代码:

int32_t push_socket_data(int fd, char* data, int32_t size) { if (NULL == data || size max_socket_buffer_size + m_iHead - m_iTail) { return -3; } if (size + m_iTail > max_socket_buffer_size) { memcopy(&pstBuffer->m_szBuffer[0], &pstBuffer->m_szBuffer[pstBuffer->m_iHead], pstBuffer->m_iTail - pstBuffer->m_iHead); pstBuffer->m_iTail -= pstBuffer->m_iHead; pstBuffer->m_iHead = 0; } memcpy(&pstBuffer->m_szBuffer[pstBuffer->m_iTail], data, size); pstBuffer->m_iTail += size; return 0; }

将缓存区数据发送出去代码:

int32_t socket_send(int fd) { stSocketBuffer* pstBuffer = get_socket_buffer(fd); if (NULL == pstBuffer) { return -1; } int32_t remainded = pstBuffer->m_iTail - pstBuffer->m_iHead; int32_t sended = 0; char* pszTmp = &pstBuffer->m_szBuffer[pstBuffer->m_iHead]; int32_t again_count = 0; while(remainded > 0 && again_count < 2) { sended = send(fd, pszTmp, (size_t)remainded, 0); if (sended > 0) { pstBuffer->m_iHead += sended; pszTmp += sended; remainded -= sended; } else if (errno == EAGAIN) { ++ again_count; continue; } else { break; } } return (size - remainded); }

​ 总结,当需要向socket发送数据时,现将数据压入发送缓存区(stSocketBuffer结构体中),并且将socket加入可写事件监听。当socket触发可写事件(EPOLLOUT)时,调用 socket_send函数发送数据,所有数据发送完毕,再清除EPOLLOUT事件。

注:转载自:https://blog.csdn.net/timid_brother/article/details/52200737

往期精彩汇总

GDB 多线程之旅

肝!动态规划

C++使用锁注意事项

呕心沥血的递归

muduo源码剖析学习总结

windows程序崩溃调试终极武器

欢迎关注公众号---后台服务器开发,更多精彩等你来看~



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有